use argon2::{
    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2,
};
use log::debug;
use rand::distr::Alphanumeric;
use rand::Rng;

use crate::common::error::ApiError;

pub struct PasswordUtil;

impl PasswordUtil {
    /// 加密密码
    pub fn hash_password(password: &str) -> Result<String, ApiError> {
        debug!("password: {}", password);

        let salt = SaltString::generate(&mut OsRng);

        let argon2 = Argon2::default();

        let password_hash = argon2
            .hash_password(password.as_bytes(), &salt)
            .map_err(|e| {
                debug!("Error hashing password: {}", e);
                ApiError::InternalServerError("Failed to hash password")
            })?
            .to_string();

        Ok(password_hash)
    }

    /// 验证密码
    pub fn verify_password(password: &str, hashed_password: &str) -> Result<bool, ApiError> {
        debug!(
            "password: {}, hashed_password: {}",
            password, hashed_password
        );

        let parsed_hash = PasswordHash::new(hashed_password).map_err(|e| {
            debug!("Error parsing password hash: {}", e);
            ApiError::InternalServerError("Failed to parse password hash")
        })?;

        let argon2 = Argon2::default();

        Ok(argon2
            .verify_password(password.as_bytes(), &parsed_hash)
            .is_ok())
    }

    /// 生成随机8位数密码，包含大小写字母和数字
    pub fn generate_random_password() -> String {
        rand::rng()
            .sample_iter(&Alphanumeric)
            .filter(|c| c.is_ascii_alphanumeric())
            .take(8)
            .map(char::from)
            .collect()
    }

    /// 校验密码是否符合规则：包含大小写字母和数字，可以有特殊字符，长度8-16
    pub fn validate_password(password: &str) -> Result<bool, ApiError> {
        if password.len() < 8 || password.len() > 16 {
            return Ok(false);
        }

        let has_lowercase = password.chars().any(|c| c.is_lowercase());
        let has_uppercase = password.chars().any(|c| c.is_uppercase());
        let has_digit = password.chars().any(|c| c.is_numeric());
        Ok(has_lowercase && has_uppercase && has_digit)
    }
}